[BZOJ2688]Green Hackenbush

[BZOJ2688]Green Hackenbush

题意: 有 \(n\) 棵随机的二叉树,每棵只知道大小为 \(a_i\)

博弈:每次选取一个子树删掉,只剩根不能操作,求先手获胜概率

考虑这个博弈,求出一棵树的 \(\text{SG}\)

显然有:

1.只有一个点的树的 \(\text{SG}\) 值为0

2.多个树组合的问题为 \(\text{SG}\) 值异或

暴力 \(dp\) ,对于树 \(T\) 求答案,设 \(T\) 所有可行的后继状态集合为 \(N(T)\) ,则得到 \(\text{SG}\) 值的表达式为

\(\text{SG}(T)=\text{mex}_{R\in N(T)}\lbrace\text{SG(R)}\rbrace\)

直接求解复杂度过高,考虑归纳性质

性质:

1.一棵根节点只有一个儿子的树,其 \(\text{SG}\) 值为儿子的 \(\text{SG}\) 值+1

考虑归纳证明:

设子树为 \(T\) ,令 \(T+u\) 表示 \(T\) 子树上面接上自己作为根,问题变为求证 \(\text{SG}(T+u)=\text{SG}(T)+1\)

设已经归纳证明所有 \(T\) 的子联通块成立

我们要求 \(\text{SG}(T+u)\)

\(\text{SG}(T+u)=\text{mex}\{\text{SG}(u),\forall _{R\in N(T)}\text{SG}(R+u)\}\)

由归纳的性质有

\(\forall _{R\subsetneq T}\text{SG}(R+T)=\text{SG}(R)+1\)

又因为 \(\text{SG}(u)=0\) ,看做把所有儿子的情况平移了1,0的位置由自己占据,因而上式成立

2.多叉树的问题可以归纳为 根分别接上每个儿子得到的树 的问题的组合

因为儿子之间实际互不干扰,比较容易理解

由此得到,一棵树的 \(\text{SG}\) 值为其所有儿子的 \(\text{SG}\) 值+1的异或和

\(dp_{n,i}\) 为一棵 \(n\) 个节点的二叉树 \(\text{SG}\) 值为 \(i\) 的概率,为了便于转移,设空树的 \(\text{SG}\) 值为-1

考虑直接枚举两棵子树的大小和 \(\text{SG}\)

考虑对于 \(n\) 个节点的二叉树,设其左儿子为 \(i\) 时的总概率为 \(F_i\)

得到的 \(\text{dp}\) 转移是

\(dp_{n,(a+1)\oplus (b+1)}\leftarrow {dp_{i,a}\cdot dp_{n-i-1,b}\cdot F_i}\)

我们知道 \(n\) 个节点的二叉树方案数为 \(Catalan(n)=\frac{(2n)!}{n!(n+1)!}\)

由此得到 \(\begin{aligned} F_i=\frac{Catalan(i)Catalan(n-i-1)}{Catalan(n)}\end{aligned}\)

此题范围可以直接带入 \(Catalan(i)\) 求解,但是依然要提一下递推的做法(似乎精度更有保障?)

\(\begin{aligned} F_i=\frac{\frac{(2i)!}{i!(i+1)!}\cdot \frac{(2n-i-2)!}{(n-i-1)!(n-i)!}}{\frac{(2n)}{n!(n+1)!}}\end{aligned}\)

递推求解 \(F_i\) ,每次 \(i\) 改变一阶乘只会改变1或者2,因此由 \(F_{i-1}\) 得到 \(F_i\) 的递推式为

\(F_i=\left\{ \begin{aligned}\frac{n(n+1)}{2n(2n-1)}&& i=0\\ F_{i-1}\cdot \frac{2i(2i-1)}{(i+1)i}\frac{(n-i+1)(n-i)}{2(n-i)(2n-2i-1)} && i\in[1,n-1]\end{aligned}\right.\)

化简之后应该是

\(F_i=\left\{ \begin{aligned}\frac{(n+1)}{2(2n-1)}&& i=0\\ F_{i-1}\cdot \frac{(2i-1)}{(i+1)}\frac{(n-i+1)}{(2n-2i-1)} && i\in[1,n-1]\end{aligned}\right.\)

至此得到一个朴素的 \(O(n^4)\) 预处理,由于是异或,可以用 \(\text{FWT}_{\oplus}\) 求解,复杂度为 \(O(n^3)\)

对于输入的每棵树,类似背包地叠加概率即可,复杂度为 \(O(n^3)\)

以下是朴素dp代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#include<bits/stdc++.h>
using namespace std;
typedef double db;
#define rep(i,a,b) for(reg int i=a,i##end=b;i<=i##end;++i)
#define drep(i,a,b) for(reg int i=a,i##end=b;i>=i##end;--i)

char IO;
template <class T=int> T rd(){
T s=0; int f=0;
while(!isdigit(IO=getchar())) if(IO=='-') f=1;
do s=(s<<1)+(s<<3)+(IO^'0');
while(isdigit(IO=getchar()));
return f?-s:s;
}

const int N=128;

int n;
db dp[N][N];

void FWT(db *a,int f){
for(int i=1;i<N;i<<=1){
for(int l=0;l<n;l+=i*2) {
for(int j=l;j<l+i;j++){
db t=a[j+i];
a[j+i]=a[j]-t;
a[j]+=t;
}
}
}
if(f==-1) rep(i,0,N-1) a[i]/=N;
}

db F[N],G[N];

int main(){
dp[0][0]=1,dp[1][0]=1;
rep(i,2,100) {
F[0]=1.0/(2*i)/(2*i-1)*(i+1)*i;
rep(j,1,i-1) {
F[j]=F[j-1] * (2*j)*(2*j-1)/(j+1)/j * 1.0/(2*(i-j))/(2*(i-j)-1)*(i-j+1)*(i-j);
}
rep(a,0,i-1) rep(h1,0,N-1) if(dp[a][h1]>0) {
rep(h2,0,N-1) if(dp[i-a-1][h2]) {
int nxt=0;
if(a>0) nxt^=h1+1;
if(i-1-a>0) nxt^=h2+1;
dp[i][nxt]+=dp[a][h1]*dp[i-a-1][h2]*F[a];
}
}
}
n=rd();
rep(i,0,N-1) F[i]=0;
F[0]=1;
rep(i,1,n) {
int x=rd();
rep(j,0,N-1) G[j]=0;
rep(j,0,N-1) if(F[j]) rep(k,0,N-1) G[j^k]+=F[j]*dp[x][k];
rep(j,0,N-1) F[j]=G[j];
}
db ans=0;
rep(i,1,N-1) ans+=F[i];
printf("%.6lf\n",ans);
}


以下是FWT优化代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#include<bits/stdc++.h>
using namespace std;
typedef double db;
#define rep(i,a,b) for(reg int i=a,i##end=b;i<=i##end;++i)
#define drep(i,a,b) for(reg int i=a,i##end=b;i>=i##end;--i)

char IO;
template <class T=int> T rd(){
T s=0; int f=0;
while(!isdigit(IO=getchar())) if(IO=='-') f=1;
do s=(s<<1)+(s<<3)+(IO^'0');
while(isdigit(IO=getchar()));
return f?-s:s;
}

const int N=128;

int n;
db dp[N][N],T[N][N];

void FWT(db *a,int f){
for(int i=1;i<N;i<<=1){
for(int l=0;l<N;l+=i*2) {
for(int j=l;j<l+i;j++){
db t=a[j+i];
a[j+i]=a[j]-t;
a[j]+=t;
}
}
}
if(f==-1) rep(i,0,N-1) a[i]/=N;
}

db F[N],G[N];

int main(){
dp[0][0]=1,dp[1][0]=1;
T[0][0]=1,T[1][1]=1;
FWT(T[0],1),FWT(T[1],1);

rep(i,2,100) {
F[0]=1.0/(2*i)/(2*i-1)*(i+1)*i;
rep(j,1,i-1) {
F[j]=F[j-1] * (2*j)*(2*j-1)/(j+1)/j * 1.0/(2*(i-j))/(2*(i-j)-1)*(i-j+1)*(i-j);
}
rep(j,0,i-1) rep(k,0,N-1) dp[i][k]+=T[j][k]*T[i-j-1][k]*F[j];
FWT(dp[i],-1);
rep(j,0,N-2) T[i][j+1]=dp[i][j];
FWT(T[i],1);
}
n=rd();
rep(i,0,N-1) F[i]=0;
F[0]=1;
rep(i,1,n) {
int x=rd();
rep(j,0,N-1) G[j]=0;
rep(j,0,N-1) if(F[j]) rep(k,0,N-1) G[j^k]+=F[j]*dp[x][k];
rep(j,0,N-1) F[j]=G[j];
}
db ans=0;
rep(i,1,N-1) ans+=F[i];
printf("%.6lf\n",ans);
}